"""
xapper file finder object definitions.
"""

import os
import sys

from django.core.exceptions import ImproperlyConfigured
from django.utils._os import safe_join
from xapper.conf import settings


_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()


class FilesystemFinder(object):
  """A path finder that searches a fixed set of directories."""

  def __init__(self, dirs=[]):
    """Initialize a new FilesystemFinder instance with a set of search
    directories.

    Args:
      dirs - (iterable of unicode) The set of search directories.
    """
    self.dirs = tuple(dirs)
    if any(not os.path.isabs(d) for d in self.dirs):
      raise ValueError("The `dirs` argument must specify absolute paths.")

  def find(self, path):
    """Search the set of directories for a given relative path.

    Args:
      path - (unicode) The relative path to search for.

    Yields:
      A series of strings containing the absolute paths of all filesystem
      entities (files, dirs, etc.) that are the concatenation of one of the
      search directories and the given relative path.
    """
    results = []
    for d in self.dirs:
      try:
        # Normalizing the case of this path makes testing a little easier on
        # Windows.
        fullpath = os.path.normcase(safe_join(d, path))
      except UnicodeDecodeError:
          # The dir name was a bytestring that wasn't valid UTF-8.
          raise
      except ValueError:
          # The joined path was located outside of template_dir.
          pass
      else:
        if os.path.exists(fullpath):
          yield fullpath


class AppDirectoryFinder(FilesystemFinder):
  """A path finder that searches all installed Django app directories."""

  def __init__(self, subdir):
    """Initialize a new AppDirectoryFinder instance to search a given
    subdirectory in all Django app directories.

    Args:
      subdir - (unicode) The name of the subdirectory in each app directory to
      search.
    """
    app_subdirs = []
    for app in settings.INSTALLED_APPS:
      i = app.rfind('.')
      if i == -1:
        m, a = app, None
      else:
        m, a = app[:i], app[i+1:]
      try:
        if a is None:
          mod = __import__(m, {}, {}, [])
        else:
          mod = getattr(__import__(m, {}, {}, [a]), a)
      except ImportError, e:
        raise ImproperlyConfigured("ImportError %s: %s" % (app, e.args[0]))
      # Normalizing the case of this path makes testing a little easier on
      # Windows.
      app_subdir = os.path.normcase(os.path.join(os.path.dirname(mod.__file__), subdir))
      if os.path.isdir(app_subdir):
        app_subdirs.append(app_subdir.decode(_fs_encoding))
    super(AppDirectoryFinder, self).__init__(app_subdirs)


filesystem_app_finder = FilesystemFinder(settings.XAPPER_SILVERLIGHT_APP_DIRS)
app_directories_app_finder = AppDirectoryFinder("silverlight")
filesystem_assembly_finder = FilesystemFinder(settings.XAPPER_ASSEMBLY_DIRS)
app_directories_assembly_finder = AppDirectoryFinder("assemblies")
